home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
SGI Developer Toolbox 6.1
/
SGI Developer Toolbox 6.1 - Disc 4.iso
/
public
/
bit
/
src
/
quant.c
< prev
next >
Wrap
C/C++ Source or Header
|
1994-08-01
|
21KB
|
813 lines
/*
* $Id: quant.c,v 0.91 1994/02/20 00:53:04 zhao Pre-Release $
*
*. This file is part of BIT shareware package. After the two weeks of
* free evaluation period, you are encouraged (required) to register
* your copy for a small registration fee, which is $35 for personal use
* and $50 for commercial, government and institutional use.
*
* Copyright(c) 1993, 1994 by T.C. Zhao.
* All rights reserved.
*
* Permission to use, copy, and distribute this software in its entirety
* for non-commercial purposes is hereby granted, provided that the
* above shareware and copyright notices and this permission notice
* appear in all copies and their documentation.
*
* This software may be modified for your own use, but modified versions
* may not be distributed without prior consent of the author.
*
* This software is provided "as is" without expressed or implied
* warranty of any kind.
*
*.
*
* Quantization:
* Q256: Based on ppmqvga.c by Bill Davidsen(davdsen@crd.ge.com)
* which was in turn based on ppmq256 and ppmq256fs by Lyle Rains
* mediancut: not implemented
* octree: not implemented
*/
#if !defined(lint) && defined(F_ID)
char *id_quant = " $Id: quant.c,v 0.91 1994/02/20 00:53:04 zhao Pre-Release $";
#endif
#include "bit.h"
#include "dmalloc.h"
#include <math.h>
/* #define Q_DEBUG */
/***** Some definations, also used by init_configsys() ******/
const char *qstring[] =
{
"Q256", "MedianCut", "Octree", 0
};
/***** init_configsys will call this routine *****/
const char *
quant_opt_string(void)
{
return "Ask|Q256|MedianCut|Octree";
}
extern int qfast256(IPTR im);
extern int qmediancut(IPTR im);
extern int qoctree(IPTR im);
/***** dithering *****/
static const char *qdstring[] =
{
"Floyd-Steinberg", "None", 0
};
enum
{
QD_fs, QD_NONE
};
const char *
qdither_opt_string(void)
{
return "Floyd-Steinberg|None";
}
/******* common limits to all quantizers **********/
#define MAXQBITS CMAPBITS /* defined in utype.h */
#ifndef RBITS
#define RBITS 5
#endif
#ifndef GBITS
#define GBITS 6
#endif
#ifndef BBITS
#define BBITS 5
#endif
#define MAXQR (1 << RBITS)
#define MAXQG (1 << GBITS)
#define MAXQB (1 << BBITS)
#define normr (PCBITS - RBITS)
#define normg (PCBITS - GBITS)
#define normb (PCBITS - BBITS)
typedef int cube_t; /* histogram type */
static cube_t ***cube; /* the actual one */
static int qcolors = 256; /* final colors */
static int qdither, qmethod; /* method and dither */
void
set_quant_parameters(int qc, int qd, int qm)
{
if (qc > 0)
qcolors = qc;
if (qd >= 0)
qdither = qd;
if (qm >= 0)
qmethod = qm;
}
/*********************************************************************
* used by type conversion routine. Basically get proper prototype
*********************************************************************/
extern int quant_method;
int
pre_quant(void)
{
return (quant_method <= 0) ?
get_quant_p("Quantization", &qmethod, &qdither, &qcolors, 0, 0, 0, 0) :
0;
}
/* get histogram, taking care of overflow. cb must be initialized */
static int
get_true_hist(IPTR im, cube_t *** cb)
{
register rgba_t *rgba = im->raster, *rs;
register unsigned r, g, b;
register int uc = 0;
show_busy("Histogram ...");
for (rs = rgba + im->w * im->h; rgba < rs; rgba++)
{
Unpack(*rgba, r, g, b);
r >>= normr;
g >>= normg;
b >>= normb;
if (!cb[r][g][b])
uc++;
if ((++(cb[r][g][b])) <= 0) /* check for overflow */
(cb[r][g][b])--;
}
end_busy();
return uc;
}
/***********************************************************
* Global routine that does the quantization
************************************************************/
int
rgb_to_cmap(IPTR im)
{
int status;
/* currently only fast256 is impletmentd */
deactivate_all_forms();
switch (qmethod)
{
default:
status = qfast256(im);
break;
}
fl_activate_all_forms();
return status;
}
/******************************************************************
* FAST 256
* Based on ppmqvga.c by Bill Davidsen(davdsen@crd.ge.com)
* which was in turn based on ppmq256 and ppmq256fs by Lyle Rains
******************************************************************{*/
#define MAXWEIGHT 128
#define STDWEIGHT_DIV (2 << 8)
#define STDWEIGHT_MUL (2 << 10)
#define GAIN 4
static int clutx;
static int weight_convert[MAXWEIGHT];
static int total_weight, cum_weight[MAXQG];
static int rep_weight, rep_threshold;
static int dr, dg, db;
static CMPTR cm;
static void
diffuse(register int r, register int g, register int b)
{
register int _7_32nds, _3_32nds, _1_16th;
if (clutx < qcolors)
{
if (cube[r][g][b] > rep_threshold)
{
cm->ct[0][clutx] = (((2 * r + 1) * PCMAX) / (2 * MAXQR));
cm->ct[1][clutx] = (((2 * g + 1) * PCMAX) / (2 * MAXQG));
cm->ct[2][clutx] = (((2 * b + 1) * PCMAX) / (2 * MAXQB));
#ifdef Q_DEBUG
if ((clutx & 3) == 0)
{
fprintf(stderr, "\n %3d (%2d): ", clutx, rep_threshold);
}
fprintf(stderr, " (%03d,%03d,%03d)", cm->ct[0][clutx],
cm->ct[1][clutx], cm->ct[2][clutx]);
#endif
++clutx;
cube[r][g][b] -= rep_weight;
}
_7_32nds = (7 * cube[r][g][b]) / 32;
_3_32nds = (3 * cube[r][g][b]) / 32;
_1_16th = cube[r][g][b] - 3 * (_7_32nds + _3_32nds);
cube[r][g][b] = 0;
/* spread error evenly in color space. */
cube[r][g][b + db] += _7_32nds;
cube[r][g + dg][b] += _7_32nds;
cube[r + dr][g][b] += _7_32nds;
cube[r][g + dg][b + db] += _3_32nds;
cube[r + dr][g][b + db] += _3_32nds;
cube[r + dr][g + dg][b] += _3_32nds;
cube[r + dr][g + dg][b + db] += _1_16th;
/*
* Conserve the error at edges if possible (which it is, except the
* last pixel)
*/
if (cube[r][g][b] != 0)
{
if (dg != 0)
cube[r][g + dg][b] += cube[r][g][b];
else if (dr != 0)
cube[r + dr][g][b] += cube[r][g][b];
else if (db != 0)
cube[r][g][b + db] += cube[r][g][b];
else
{
M_info("Quant", "lost error term");
}
}
}
cube[r][g][b] = -1;
}
static int
nearest_color(register rgba_t rgba)
{
static unsigned long lut[3][2 * PCMAX + 1];
register unsigned long *tlut1 = lut[0] + PCMAX;
register unsigned long *tlut2 = lut[1] + PCMAX;
register unsigned long *tlut3 = lut[2] + PCMAX;
register unsigned i;
register unsigned long min_dist_sqd, dist_sqd;
register unsigned r, g, b;
register cube_t *cache;
register int nearest = 0;
static int lutinit;
if (!lutinit)
{
for (i = 0; i < PCMAX; i++)
{
tlut1[i] = tlut1[-i] = 3 * (i * i);
tlut2[i] = tlut2[-i] = 4 * (i * i);
tlut3[i] = tlut3[-i] = 2 * (i * i);
}
lutinit = 1;
}
Unpack(rgba, r, g, b);
cache = &(cube[r >> normr][g >> normg][b >> normb]);
if (*cache >= 0 && *cache < qcolors)
return *cache;
min_dist_sqd = ~0;
for (i = 0; i < qcolors; ++i)
{
dist_sqd = *(tlut1 + r - cm->ct[0][i]) +
*(tlut2 + g - cm->ct[1][i]) +
*(tlut3 + b - cm->ct[2][i]);
if (dist_sqd < min_dist_sqd)
{
nearest = i;
min_dist_sqd = dist_sqd;
}
}
return (*cache = nearest);
}
/* Errors are carried at FS_SCALE times actual size for accuracy */
#define _7x16ths(x) ((7 * (x)) / 16)
#define _5x16ths(x) ((5 * (x)) / 16)
#define _3x16ths(x) ((3 * (x)) / 16)
#define _1x16th(x) ((x) / 16)
#define NEXT(line) (!(line))
#define FS_SCALE 1024
typedef int fs_err_array[2][3];
#if 0 /* disable function version of diffuse code */
static void
fs_diffuse(fs_err_array * fs_err, int line, int c, int err)
{
fs_err[1][line][c] += _7x16ths(err);
fs_err[-1][NEXT(line)][c] += _3x16ths(err);
fs_err[0][NEXT(line)][c] += _5x16ths(err);
fs_err[1][NEXT(line)][c] = _1x16th(err);
}
#else
#define fs_diffuse(fs_err, line, c, err) \
do { \
fs_err[1][line][c] += _7x16ths(err); \
fs_err[-1][NEXT(line)][c] += _3x16ths(err); \
fs_err[0][NEXT(line)][c] += _5x16ths(err); \
fs_err[1][NEXT(line)][c] = _1x16th(err); \
} while (ZERO)
#endif
typedef int erropt_t[4];
/* the main routine for fast256 */
int
qfast256(IPTR im)
{
register rgba_t *rgba = im->raster, *rs;
register int r, g, b;
int i, j, k, fs_line = 0, row, ok;
int nearest, *errP;
fs_err_array *fs_err_lines = 0, *fs_err = 0;
register ci_t *ci, **cci;
register int scmax = FS_SCALE * PCMAXV;
static erropt_t *erropt;
long quant_rlines;
check_emergency();
clutx = 0;
/* get all the memory needed before we do anything */
set_use_calloc(1);
ok = ((erropt = calloc(qcolors, sizeof(erropt_t))) != 0 &&
(cube = get_3d(MAXQR, MAXQG, MAXQB, sizeof(cube_t))) != 0);
set_use_calloc(0);
if (!ok)
{
Free(erropt);
free_3d(cube);
Bark("Quant", "malloc failed");
return -1;
}
if (qdither == QD_fs)
{
if (!(fs_err_lines = calloc(im->w + 2, sizeof(fs_err_array))))
{
Bark("Quant", "malloc failed");
Free(erropt);
free_3d(cube);
return -1;
}
}
/* get no. of colors with clustering */
cm = im->cmap;
check_emergency();
(void) get_true_hist(im, cube);
/* get weight */
/* Initialize logarithmic weighing table */
weight_convert[0] = weight_convert[1] = 0;
for (i = 2; i < MAXWEIGHT; ++i)
{
weight_convert[i] = (int) (100.0 * log((double) (i)));
}
k = im->w * im->h;
if ((k /= STDWEIGHT_DIV) == 0)
k = 1;
total_weight = i = 0;
quant_rlines = progress_report("Generating weight ...", MAXQG);
for (g = 0; g < MAXQG; g++)
{
REPORT(g, quant_rlines);
for (r = 0; r < MAXQR; r++)
{
register unsigned long weight;
for (b = 0; b < MAXQB; b++)
{
/* Normalize the weights, independent of picture size. */
weight = cube[r][g][b] * STDWEIGHT_MUL;
if ((weight /= k))
i++;
if (weight >= MAXWEIGHT)
weight = MAXWEIGHT - 1;
total_weight += (cube[r][g][b] = weight_convert[weight]);
}
}
cum_weight[g] = total_weight;
}
rep_weight = total_weight / qcolors;
sprintf(im->misc, "c=%d d=%s u=%d", qcolors,
(qdither == QD_fs) ? "y" : "n", i);
update_image_info(im);
im->colors = i;
/* select threshold */
rep_threshold = total_weight * (28 + 110000 / i) / 95000;
#ifdef Q_DEBUG
fprintf(stderr, "found %d colors with total weight %d\n", i, total_weight);
fprintf(stderr, "avg weight for colors used = %7.2f\n",
(float) total_weight / i);
fprintf(stderr, "avg weight for all colors = %7.2f\n",
(float) total_weight / (MAXQR * MAXQG * MAXQB));
fprintf(stderr, "avg weight for final colors = %4d\n", rep_weight);
fprintf(stderr, "final rep_threshold = %d\n", rep_threshold);
#endif
quant_rlines = progress_report("Getting threshold ...", MAXQG);
dg = 1;
for (g = 0; g < MAXQG; ++g)
{
dr = 1;
for (r = 0; r < MAXQR; ++r)
{
db = 1;
for (b = 0; b < MAXQB - 1; ++b)
diffuse(r, g, b);
db = 0;
diffuse(r, g, b);
++b;
if (++r == MAXQR - 1)
dr = 0;
db = -1;
while (--b > 0)
diffuse(r, g, b);
db = 0;
diffuse(r, g, b);
}
if ((j = clutx - (qcolors * cum_weight[g]) / total_weight) != 0)
{
rep_threshold += j * GAIN;
}
if (++g == MAXQG - 1)
dg = 0;
dr = -1;
while (r-- > 0)
{
db = 1;
for (b = 0; b < MAXQB - 1; ++b)
diffuse(r, g, b);
db = 0;
diffuse(r, g, b);
++b;
if (--r == 0)
dr = 0;
db = -1;
while (--b > 0)
diffuse(r, g, b);
db = 0;
diffuse(r, g, b);
}
/* Modify threshold to keep rep points proportionally distribited */
if ((j = clutx - (qcolors * cum_weight[g]) / total_weight) != 0)
{
rep_threshold += j * GAIN;
}
if ((++ok % 8) == 0)
update_progress_report(g);
M_info("quant", "diffuseG %d", g);
}
M_info("quant", "DiffuseDone");
/* check errors and reduce colormap */
quant_rlines = progress_report("Reducing colors ...", im->h);
for (row = im->h - 1; row >= 0; row--)
{
REPORT(im->h - 1 - row, quant_rlines);
rgba = ((rgba_t **) im->mraster)[row];
for (rs = rgba + im->w; rgba < rs; rgba++)
{
nearest = nearest_color(*rgba);
CPACK2RGB(*rgba, r, g, b);
errP = erropt[nearest];
errP[0] += r - cm->ct[0][nearest];
errP[1] += g - cm->ct[1][nearest];
errP[2] += b - cm->ct[2][nearest];
++errP[3];
}
}
#ifdef Q_DEBUG
fprintf(stderr, "Color Red Err Green Err Blue Err Count\n");
#endif
for (i = 0; i < qcolors; i++)
{
errP = erropt[i];
j = errP[3];
if (j > 0)
{
j *= 4;
#ifdef Q_DEBUG
fprintf(stderr, "%4d %10d %10d %10d %6d",
i, errP[0] / j, errP[1] / j, errP[2] / j, j);
#endif
cm->ct[0][i] += (errP[0] / j) * 4;
cm->ct[1][i] += (errP[1] / j) * 4;
cm->ct[2][i] += (errP[2] / j) * 4;
}
}
/* reset cache */
for (r = 0; r < MAXQR; r++)
for (g = 0; g < MAXQG; g++)
for (b = 0; b < MAXQB; b++)
cube[r][g][b] = -1;
/************* map colors **********************/
cm->colors = clutx;
if (clutx < im->colors)
im->colors = clutx;
quant_rlines = progress_report("Maping colors ...", im->h);
if (!(cci = get_mat(im->h, im->w, sizeof(ci_t))))
return -1;
for (row = 0; row < im->h; row++)
{
REPORT(row, quant_rlines);
if (qdither == QD_fs)
{
fs_err = fs_err_lines + 1;
fs_err[0][NEXT(fs_line)][0] = 0;
fs_err[0][NEXT(fs_line)][1] = 0;
fs_err[0][NEXT(fs_line)][2] = 0;
}
rgba = ((rgba_t **) im->mraster)[row];
ci = cci[row];
for (rs = rgba + im->w; rgba < rs; rgba++, ci++)
{
if (qdither == QD_fs)
{
CPACK2RGB(*rgba, r, g, b);
r = FS_SCALE * r + fs_err[0][fs_line][0];
Range(r, 0, scmax);
g = FS_SCALE * g + fs_err[0][fs_line][1];
Range(g, 0, scmax);
b = FS_SCALE * b + fs_err[0][fs_line][2];
Range(b, 0, scmax);
*rgba = RGB2CPACK((r / FS_SCALE),
(g / FS_SCALE),
(b / FS_SCALE));
}
nearest = nearest_color(*rgba);
if (qdither == QD_fs)
{
r -= FS_SCALE * cm->ct[0][nearest];
g -= FS_SCALE * cm->ct[1][nearest];
b -= FS_SCALE * cm->ct[2][nearest];
fs_diffuse(fs_err, fs_line, 0, r);
fs_diffuse(fs_err, fs_line, 1, g);
fs_diffuse(fs_err, fs_line, 2, b);
}
*ci = nearest;
if (qdither == QD_fs)
++fs_err;
}
fs_line = NEXT(fs_line);
}
M_info("quant", "Done");
#ifdef Q_DEBUG
fprintf(stderr, "Final Colors\n");
for (i = 0; i < qcolors; i++)
fprintf(stderr, "%4d: %3d %3d %3d\n", i, cm->ct[0][i],
cm->ct[1][i], cm->ct[2][i]);
#endif
if (qdither == QD_fs)
free(fs_err_lines);
Free(erropt);
free_3d(cube);
(void) fill_image_struct(im, cci, im->h, im->w,
IS_GRAY(im) ? T_GMAP : T_CMAP);
M_info("QuantReplaceImage", "Done");
remove_progress_report();
return 0;
}
/***********************************************************
* End of Qfast256
************************************************************}*/
/***************************************************************
* GUI part of the quantization
***************************************************************/
static FL_FORM *quantform;
static FL_OBJECT *qtitle, *mchoice, *dichoice, *qreport, *qcancel, *qok;
static FL_OBJECT *qmisc1, *qmisc2;
static int qnc = 256, qncmax = MAXCML;
static void create_form_quant(void);
void
set_quant_max_color(int c)
{
qncmax = c;
}
/* ARGSUSED */
static void
qnc_cb(FL_OBJECT * ob, long q)
{
char pp[10];
if (q == 2)
qnc <<= 1;
else if (q == -2)
qnc >>= 1;
else
qnc += q;
if (qnc < 8)
qnc = 8;
else if (qnc > qncmax)
qnc = qncmax;
sprintf(pp, "%d", qnc);
fl_set_object_label(qreport, pp);
}
int
get_quant_p(const char *title, int *qm, int *dm, int *qc,
const char *misc1, int *s1, const char *misc2, int *s2)
{
FL_OBJECT *ret;
short val;
create_form_quant();
qnc = *qc;
qnc_cb(0, 0);
fl_set_object_label(qtitle, title);
/* minus one because qstring is one more than available */
if (*qm < 0 || *qm >= (sizeof(qstring) / sizeof(char *)) - 1)
*qm = 0;
if (*dm < 0 || *dm >= (sizeof(qdstring) / sizeof(char *)) - 1)
*dm = 0;
fl_set_choice(mchoice, *qm + 1);
fl_set_choice(dichoice, *dm + 1);
if (misc1 && *misc1)
{
fl_set_object_label(qmisc1, misc1);
fl_set_button(qmisc1, *s1);
}
else
{
fl_hide_object(qmisc1);
}
if (misc2 && *misc2)
{
fl_set_object_label(qmisc2, misc2);
fl_set_button(qmisc1, *s2);
}
else
{
fl_hide_object(qmisc2);
}
bit_show_form(quantform, FL_PLACE_HOTSPOT, 0, title);
while ((ret = fl_do_forms()) != qcancel && ret != qok)
{
if (ret == FL_EVENT)
(void) bit_qread(&val);
}
if (ret != qcancel)
{
*qc = qnc;
*qm = fl_get_choice(mchoice) - 1;
*dm = fl_get_choice(dichoice) - 1;
if (misc1 && *misc1)
*s1 = fl_get_button(qmisc1);
if (misc2 && *misc2)
*s2 = fl_get_button(qmisc2);
}
bit_hide_form(quantform);
qncmax = MAXCML;
return (ret == qcancel) ? -1 : 0;
}
static void
create_form_quant(void)
{
FL_OBJECT *obj;
int i;
static int ok;
if (ok)
return;
quantform = fl_bgn_form(FL_NO_BOX, 225.0, 200.0);
obj = fl_add_box(FL_UP_BOX, 0.0, 0.0, 225.0, 200.0, "");
fl_set_object_lcol(obj, 4);
obj = fl_add_button(FL_HIDDEN_BUTTON, 0, 0, 225, 200.0, "");
fl_set_call_back(obj, help_cb, HELP_QUANT);
qtitle = obj = fl_add_text(FL_NT, 35.0, 165.0, 175.0, 25.0, "");
fl_set_object_color(obj, 4, 47);
fl_set_object_lcol(obj, 4);
fl_set_object_align(obj, FL_ALIGN_CENTER);
fl_set_object_lstyle(obj, FL_BOLD_STYLE);
/* quant method */
mchoice = obj = fl_add_choice(FL_NC, 60.0, 130.0, 150.0, 25.0, "Method");
fl_set_object_boxtype(obj, FL_FRAME_BOX);
fl_set_object_lcol(obj, 4);
fl_set_object_lsize(obj, 10.0);
fl_set_choice_fontsize(obj, 10.0);
i = 0;
while (qstring[i])
{
fl_addto_choice(obj, qstring[i]);
i++;
}
dichoice = obj = fl_add_choice(FL_NC, 60.0, 100.0, 150.0, 25.0, "Dither");
fl_set_object_boxtype(obj, FL_FRAME_BOX);
fl_set_object_lcol(obj, 4);
fl_set_object_lsize(obj, 10.0);
fl_set_choice_fontsize(obj, 10.0);
i = 0;
while (qdstring[i])
{
fl_addto_choice(obj, qdstring[i]);
i++;
}
/* no. of colors */
obj = fl_add_button(FL_TB, 185.0, 70.0, 25.0, 25.0, "@>>");
fl_set_object_boxtype(obj, FL_FRAME_BOX);
fl_set_object_color(obj, 47, 9);
fl_set_object_lcol(obj, 1);
fl_set_call_back(obj, qnc_cb, 2);
obj = fl_add_button(FL_TB, 160.0, 70.0, 25.0, 25.0, "@>");
fl_set_object_boxtype(obj, FL_FRAME_BOX);
fl_set_object_color(obj, 47, 9);
fl_set_object_lcol(obj, 1);
fl_set_call_back(obj, qnc_cb, 1);
obj = fl_add_button(FL_TB, 60.0, 70.0, 25.0, 25.0, "@<<");
fl_set_object_boxtype(obj, FL_FRAME_BOX);
fl_set_object_color(obj, 47, 9);
fl_set_object_lcol(obj, 1);
fl_set_call_back(obj, qnc_cb, -2);
obj = fl_add_button(FL_TB, 85.0, 70.0, 25.0, 25.0, "@<");
fl_set_object_boxtype(obj, FL_FRAME_BOX);
fl_set_object_color(obj, 47, 9);
fl_set_object_lcol(obj, 1);
fl_set_call_back(obj, qnc_cb, -1);
qreport = obj = fl_add_text(FL_NT, 110.0, 70.0, 50.0, 25.0, "");
fl_set_object_boxtype(obj, FL_FRAME_BOX);
fl_set_object_lcol(obj, 4);
fl_set_object_lsize(obj, 10.0);
fl_set_object_align(obj, FL_ALIGN_CENTER);
obj = fl_add_text(FL_NT, 5.0, 70.0, 50.0, 25.0, "Ncolors");
fl_set_object_lcol(obj, 4);
fl_set_object_lsize(obj, 10.0);
/* misc buttons */
qmisc1 = obj = fl_add_roundbutton(FL_PB, 15.0, 35.0, 35.0, 30.0, "");
fl_set_object_lcol(obj, 4);
fl_set_object_lsize(obj, 10.0);
qmisc2 = obj = fl_add_roundbutton(FL_PB, 125.0, 35.0, 35.0, 30.0, "");
fl_set_object_lcol(obj, 4);
fl_set_object_lsize(obj, 10.0);
qcancel = obj = fl_add_button(FL_NB, 55.0, 10.0, 70.0, 25.0, "Cancel");
fl_set_object_color(obj, 47, 3);
fl_set_object_lsize(obj, 10.0);
qok = obj = fl_add_button(FL_NB, 125.0, 10.0, 70.0, 25.0, "OK");
fl_set_object_color(obj, 47, 2);
fl_set_object_lsize(obj, 10.0);
fl_end_form();
fl_set_form_hotspot(quantform, (125 + 35), 10 + 13);
ok = 1;
}